<?php
/**
 * Created by PhpStorm.
 * User: lcz
 * Date: 2018/8/8
 * Time: 9:52
 * 微信相关操作
 */
namespace app\common\library;

use app\common\library\log\Log;
use think\Cache;
use think\Request;

class Weixin{
	
	//小程序ID
	private $smallAppID;
	//小程序的 app secret
	private $smallAppSecret;
	//小程序支付密钥
	private $payKey;
	//微信支付商户号
	private $mchId;
	
	private $unifiedorderUrl = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
	public function __construct($appid = ''){
		if(!$appid) {
			$appid = Request::instance()->header('appid');
		}
		
		$config = config('weixin');
		if($appid == config('weixin.public.app_id')){
			//公众号
			$appSecret = $config['public']['app_secret'];
		} else {
			//小程序
			$appSecret = $config['smallapp']['app_secret'];
		}
		$this->smallAppSecret = $appSecret;
		$this->smallAppID = $appid;
		$this->payKey = $config['pay_key'];
		$this->mchId = $config['mch_id'];
	}
	
	/**
	 * 获取微信accesstoken
	 * @return bool|mixed
	 */
	public function getAccessToken(){
		$key = $this->smallAppID.'AccessToken';
		if(!$token = Cache::get($key)){
			$url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='.$this->smallAppID.'&secret='.$this->smallAppSecret;
			$res = curl_get_info($url);
			if(isset($res['access_token']) && !empty($res['access_token'])){
				Cache::set($key, $res['access_token'], $res['expires_in']-200);
				$token = $res['access_token'];
			}
		}
		
		if($token){
			return $token;
		}
		return false;
	}
	
	/**
	 * 发送模板消息
	 * @param $touser 必填 接收者（用户）的 openid
	 * @param $templateId 必填 所需下发的模板消息的id
	 * @param $formId 必填 表单提交场景下，为 submit 事件带上的 formId；支付场景下，为本次支付的 prepay_id
	 * @param array $data 必填 模板内容，不填则下发空模板
	 * @param string $page 选填 点击模板卡片后的跳转页面，仅限本小程序内的页面。支持带参数,（示例index?foo=bar）。该字段不填则模板无跳转
	 * @param string $keyword 选填 	模板需要放大的关键词，不填则默认无放大
	 * @return bool
	 */
	public function sendTemplate($touser, $templateId, $formId, $data = [], $page='', $keyword = ''){
		$token = $this->getAccessToken();
		if(!$token){
			$this->error('token获取出错');
		}
		$url = 'https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token='.$token;
		
		$postData = [
			'touser' => $touser,
			'template_id' => $templateId,
			'form_id' => $formId,
			'data' => $data,
			'page' => $page,
			'emphasis_keyword' => $keyword,
		];
		$res = curl_get_info($url,'post',json_encode($postData));
		if(isset($res['errcode']) && $res['errcode'] == 0){
			return true;
		} else {
			return $res['errmsg'];
		}
	}
	
	
	/**
	 * 统计下单
	 * @param $no 订单号
	 * @param $price 价格
	 * @param $notifyUrl 回调地址
	 * @param $openId 用户openId
	 * @return array
	 */
	public function unifiedOrder($no, $price, $notifyUrl, $openId) {
		$no = $no.'-'.time();
		$data = [
			'appid' => $this->smallAppID, //小程序ID 微信分配的小程序ID
			'mch_id' => $this->mchId, //商户号 微信支付分配的商户号
//			'device_info' => '', //设备号(非必填)
			'nonce_str' => $this->createNoncestr(), //随机字符串 随机字符串，不长于32位
//			'sign_type' => '', //签名类型(非必填)
			'body' => '订单支付', //商品描述
//			'detail' => '', //商品详情(非必填)
			'attach' => 'order', //附加数据(非必填)
			'out_trade_no' => $no, //商户订单号
//			'fee_type' => '', //货币类型(非必填)
			'total_fee' => $price * 100, //总金额
			'spbill_create_ip' => Request::instance()->ip(), //终端IP
//			'time_start' => '', //交易起始时间(非必填)
//			'time_expire' => '', //交易结束时间(非必填)
//			'goods_tag' => '', //商品标记(非必填)
			'notify_url' => $notifyUrl, //通知地址
			'trade_type' => 'JSAPI', //交易类型
//			'limit_pay' => '', //指定支付方式(非必填)
			'openid' => $openId, //用户标识
		];
		
		
		$data['sign'] = $this->getSign($data, $this->payKey);
		$xmlData = $this->arrayToXml($data);
		$res = $this->xmlToArray($this->postXmlCurl($xmlData, $this->unifiedorderUrl, 60));
		if (isset($res['result_code']) && $res['result_code'] == 'SUCCESS') {
			$resData = [
				'appId' => $this->smallAppID,
				'timeStamp' => (string)time(),
				'nonceStr' => (string)time(),
				'package' => 'prepay_id=' . $res['prepay_id'],
				'signType' => 'MD5',
			];
			
			$resData['paySign'] = $this->getSign($resData, $this->payKey);
			$resData['prepay_id'] = $res['prepay_id'];
			
			return [
				'code' => 1,
				'msg' => '生成订单成功',
				'data' => $resData
			];
		} else {
			return [
				'code' => 0,
				'msg' => '订单创建出错',
				'data' => $res
			];
		}
	}
	
	private static function postXmlCurl($xml, $url, $second = 30, $cert = false)
	{
		$ch = curl_init();
		//设置超时
		curl_setopt($ch, CURLOPT_TIMEOUT, $second);
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //严格校验
		//设置header
		curl_setopt($ch, CURLOPT_HEADER, false);
		
		if($cert) {
			$certPath = ROOT_PATH.'/application/common/wx_cert/apiclient_cert.pem';
			$keyPath  = ROOT_PATH.'/application/common/wx_cert/apiclient_key.pem';
			curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLCERT,$certPath);
			curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
			curl_setopt($ch,CURLOPT_SSLKEY,$keyPath);
		}
		
		
		//要求结果为字符串且输出到屏幕上
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		//post提交方式
		curl_setopt($ch, CURLOPT_POST, true);
		curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
		
		
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
		curl_setopt($ch, CURLOPT_TIMEOUT, 40);
		set_time_limit(0);
		
		
		//运行curl
		$data = curl_exec($ch);
		//返回结果
		if ($data) {
			curl_close($ch);
			return $data;
		} else {
			$error = curl_errno($ch);
			curl_close($ch);
			return false;
		}
	}
	
	
	//数组转换成xml
	public static function arrayToXml($arr) {
		$xml = "<root>";
		foreach ($arr as $key => $val) {
			if (is_array($val)) {
				$xml .= "<" . $key . ">" . arrayToXml($val) . "</" . $key . ">";
			} else {
				$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
			}
		}
		$xml .= "</root>";
		return $xml;
	}
	
	
	//xml转换成数组
	public static function xmlToArray($xml) {
		//禁止引用外部xml实体
		libxml_disable_entity_loader(true);
		
		$xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
		
		$val = json_decode(json_encode($xmlstring), true);
		return $val;
	}
	
	
	//作用：产生随机字符串，不长于32位
	public static function createNoncestr($length = 32) {
		$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
		$str = "";
		for ($i = 0; $i < $length; $i++) {
			$str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
		}
		return $str;
	}
	
	
	//作用：生成签名
	private static function getSign($Obj, $key) {
		foreach ($Obj as $k => $v) {
			if ($v) {
				$Parameters[$k] = $v;
			}
		}
		//签名步骤一：按字典序排序参数
		ksort($Parameters);
		$String = self::formatBizQueryParaMap($Parameters, false);
		//签名步骤二：在string后加入KEY
		$String = $String . "&key=" . $key;
		//签名步骤三：MD5加密
		$String = md5($String);
		//签名步骤四：所有字符转为大写
		$result_ = strtoupper($String);
		return $result_;
	}
	
	
	///作用：格式化参数，签名过程需要使用
	public static function formatBizQueryParaMap($paraMap, $urlencode) {
		$buff = "";
		ksort($paraMap);
		foreach ($paraMap as $k => $v) {
			if ($urlencode) {
				$v = urlencode($v);
			}
			$buff .= $k . "=" . $v . "&";
		}
		$reqPar = '';
		if (strlen($buff) > 0) {
			$reqPar = substr($buff, 0, strlen($buff) - 1);
		}
		return $reqPar;
	}
	
	
	public static function notify($path) {
		header('Content-type:text/html;charset=utf8');
		
		$log = new Log();
		$log::info('===================' . date('Y-m-d H:i:s') . "==============\r\n", $path);
		
		$xml = isset($GLOBALS['HTTP_RAW_POST_DATA'])?$GLOBALS['HTTP_RAW_POST_DATA']:file_get_contents('php://input', 'r');
		$xmlObj = simplexml_load_string($xml, 'SimplexmlElement', LIBXML_NOCDATA);
		
		$log::info(json_encode($xmlObj) . "\r\n", $path);
		$data = json_decode(json_encode($xmlObj), true);
		
		$key = config('weixin.pay_key');
		
		$data_sign = $data['sign'];
		// sign不参与签名算法
		unset($data['sign']);
		
		$sign = self::getSign($data, $key);
		
		// 判断签名是否正确  判断支付状态
		if (($sign !== $data_sign) || ($data['return_code'] != 'SUCCESS') || ($data['result_code'] != 'SUCCESS')) {
			$log::info('验证失败', $path);
			return false;
		} else {
			$log::info('验证成功', $path);
			return $data;
		}
		
	}
	
	
	/**
	 * 获取用户信息
	 * @param $openid 用户Token
	 * @return mixed 数组的用户信息
	 */
	public function getUserInfo($openid)
	{
		//获取用户信息
		$get_user_info_url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" . $this->getAccessToken() . "&openid=" . $openid . "&lang=zh_CN";
		$userinfo = curl_get_info($get_user_info_url);
		return $userinfo;
	}
	
	
	/**
	 * 生成小程序二给码
	 * @param $path
	 * @return bool|string
	 */
	public function createQrCode($path) {
		//增加缓存检查，每分钟腾讯有此数限制
		$fileName = md5($this->smallAppID.$path).".png";
		
		$dir = ROOT_PATH."public/uploads/wxaqrcode";
		if(!is_dir($dir)) {
			mkdir($dir, 0777, true);
		}
		$imgPath = $dir.DS.$fileName;
		
		$returnUrl = Request::instance()->domain()."/uploads/wxaqrcode/".$fileName;
		if(file_exists($imgPath) && filesize($imgPath) > 1000){//判断文件存在并且有一定大小
			return $returnUrl;
		}
		
		$token = $this->getAccessToken();
		$url = "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=$token";
		$postData = [
			"path"   => $path,
		];
		
		$res = curl_get_info($url, 'post', json_encode($postData), 20, false, ['Content-Type: text/plain']);
		if($res){
			$saveRes = file_put_contents($imgPath, $res);
			if(!$saveRes) {
				return false;
			}
		}
		return $returnUrl;
	}
	
	public function getTicket(){
		
		if (!$tickey = cache('weixin_tickey')) {
			$url = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token='.$this->getAccessToken().'&type=jsapi';
			$arr = curl_get_info($url);
			//如果不出错，生成缓存
			if (isset($arr['ticket'])) {
				cache('weixin_tickey', $arr['ticket'], $arr['expires_in']);
			}
			$tickey = $arr['ticket'];
		}
		return $tickey;
	}
	
	
	/**
	 * 微信退款
	 * @param $transactionId 支付流水号
	 * @param $refundNo 退款订单号
	 * @param $totalFee 订单总金额（分）
	 * @param $refundFee 退款金额(分）
	 * @param String $desc 退款原因
	 * @return array
	 */
	public function refund($transactionId, $refundNo, $totalFee, $refundFee, $desc = '') {
		$url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
		$notifyUrl = Request::instance()->domain().url('api/notify/wxRefund','','');
		$data = [
			'appid' => $this->smallAppID,
			'mch_id' => $this->mchId,
			'nonce_str' => self::createNoncestr(),
			'transaction_id' => $transactionId,
			'out_refund_no' => $refundNo,
			'total_fee' => $totalFee*100,
			'refund_fee' => $refundFee*100,
			'refund_desc' => $desc,
			'notify_url' => $notifyUrl,
		];
		$data['sign'] = $this->getSign($data, $this->payKey);
		$xmlData = $this->arrayToXml($data);
		$res = $this->xmlToArray($this->postXmlCurl($xmlData, $url, 60, true));
		if (isset($res['result_code']) && $res['result_code'] == 'SUCCESS') {
			return true;
		} else {
			return false;
		}
		
	}
	
	
	
	/**
	 * 创建广告数据源
	 * @return bool
	 */
	public function createSorce(){
		$access_token = $this->getAccessToken();
		$url = "https://api.weixin.qq.com/marketing/user_action_sets/add?version=v1.0&access_token=".$access_token;
		$header=[
			"Content-Type: application/json;charset=utf-8",
		];
		$data = [
			"type" => "WEB",
			"name" => "enrollment",
			"description" => "招生广告"
		];
		
		$res = curl_get_info($url, 'post', json_encode($data), 30, true, $header);
		if(isset($res['data']['user_action_set_id'])) {
			return $res['data']['user_action_set_id'];
		}
		return false;
	}
	
	/**
	 * 上报广告数据
	 * @param $click_id
	 * @param string $type
	 * @return bool
	 */
	public function upMp($click_id, $type = 'COMPLETE_ORDER'){
		$user_act_set_id = '1109734775';
		$access_token = $this->getAccessToken();
		$timestamp = time();
		$url = "https://api.weixin.qq.com/marketing/user_actions/add?version=v1.0&access_token=".$access_token;
		$header=[
			"Content-Type: application/json;charset=utf-8",
		];
		$post_data = [
			"actions" =>[[
				"user_action_set_id" => $user_act_set_id,
				"url" => "http://www.qq.com",
				"action_time" => $timestamp,
				"action_type" => $type,
				"trace" => [
					"click_id" => $click_id
				]
			]]
		];
		
		$res = curl_get_info($url, 'post', json_encode($post_data), 30, true, $header);
		if($res['errcode'] == 0) {
			return true;
		}
		
		return $res;
	}
	
	
	/**
	 * 获取用户openID
	 * @param $code
	 * @return mixed
	 */
	public function getOpenId($code) {
		$url = 'https://api.weixin.qq.com/sns/jscode2session?appid=' . $this->smallAppID . '&secret=' . $this->smallAppSecret . '&js_code=' . $code . '&grant_type=authorization_code';
		return curl_get_info($url);
	}
}